﻿
Ext.define('Ext.event.publisher.Gesture', {
    extend: 'Ext.event.publisher.Dom',
    alternateClassName: 'Ext.event.publisher.TouchGesture',

    requires: [
        'Ext.util.Point',
        'Ext.AnimationQueue'
    ],

    config: {
        recognizers: {}
    },

    isCancelEvent: {
        touchcancel: 1,
        pointercancel: 1,
        MSPointerCancel: 1
    },

    constructor: function (config) {
        var me = this,
            onTouchStart = me.onTouchStart,



            onTouchMove = me.onTouchMove =



                Ext.Function.createAnimationFrame(me.onTouchMove, me),
            onTouchEnd = me.onTouchEnd =




                Ext.Function.createAnimationFrame(me.onTouchEnd, me, null, 1);

        me.handlers = {
            touchstart: onTouchStart,
            touchmove: onTouchMove,
            touchend: onTouchEnd,
            touchcancel: onTouchEnd,
            pointerdown: onTouchStart,
            pointermove: onTouchMove,
            pointerup: onTouchEnd,
            pointercancel: onTouchEnd,
            MSPointerDown: onTouchStart,
            MSPointerMove: onTouchMove,
            MSPointerUp: onTouchEnd,
            MSPointerCancel: onTouchEnd,
            mousedown: onTouchStart,
            mousemove: onTouchMove,
            mouseup: onTouchEnd
        };


        me.recognizedEvents = {};

        me.activeTouchesMap = {};
        me.activeTouches = [];
        me.changedTouches = [];


        if (Ext.supports.TouchEvents) {

            me.onTargetTouchMove = me.onTargetTouchMove.bind(me);
            me.onTargetTouchEnd = me.onTargetTouchEnd.bind(me);
        }

        me.initConfig(config);

        return me.callParent();
    },

    applyRecognizers: function (recognizers) {
        var name, recognizer;

        for (name in recognizers) {
            recognizer = recognizers[name];

            if (recognizer) {
                this.registerRecognizer(recognizer);
            }
        }

        return recognizers;
    },

    handles: function (eventName) {
        var handledEvents = this.handledEventsMap;

        return !!handledEvents[eventName] || !!handledEvents['*'] || eventName === '*' ||
                this.recognizedEvents.hasOwnProperty(eventName);
    },

    registerRecognizer: function (recognizer) {
        var me = this,
            recognizedEvents = me.recognizedEvents,
            handledEvents = recognizer.getHandledEvents(),
            i, ln;



        recognizer.setOnRecognized(me.onRecognized);
        recognizer.setCallbackScope(me);




        for (i = 0, ln = handledEvents.length; i < ln; i++) {
            recognizedEvents[handledEvents[i]] = 1;
        }
    },

    onRecognized: function (eventName, e, info) {
        var me = this,
            changedTouches = e.changedTouches,
            ln = changedTouches.length,
            targetGroups, targets, i, touch;

        info = info || {};







        info.type = eventName;










        info.target = changedTouches[0].target;



        info.isStopped = false;

        e = e.chain(info);

        if (ln > 1) {
            targetGroups = [];
            for (i = 0; i < ln; i++) {
                touch = changedTouches[i];
                targetGroups.push(touch.targets);
            }

            targets = me.getCommonTargets(targetGroups);
        } else {
            targets = changedTouches[0].targets;
        }

        me.publish(eventName, targets, e);
    },

    getCommonTargets: function (targetGroups) {
        var firstTargetGroup = targetGroups[0],
            ln = targetGroups.length;

        if (ln === 1) {
            return firstTargetGroup;
        }

        var commonTargets = [],
            i = 1,
            target, targets, j;

        while (true) {
            target = firstTargetGroup[firstTargetGroup.length - i];

            if (!target) {
                return commonTargets;
            }

            for (j = 1; j < ln; j++) {
                targets = targetGroups[j];

                if (targets[targets.length - i] !== target) {
                    return commonTargets;
                }
            }

            commonTargets.unshift(target);
            i++;
        }

        return commonTargets;
    },

    invokeRecognizers: function (methodName, e) {
        var recognizers = this.getRecognizers(),
            name, recognizer;

        if (methodName === 'onStart') {
            for (name in recognizers) {
                recognizers[name].isActive = true;
            }
        }

        for (name in recognizers) {
            recognizer = recognizers[name];
            if (recognizer.isActive && recognizer[methodName].call(recognizer, e) === false) {
                recognizer.isActive = false;
            }
        }
    },

    updateTouches: function (e, isEnd) {
        var me = this,
            browserEvent = e.browserEvent,




            touchSources = browserEvent.changedTouches || [browserEvent],
            activeTouches = me.activeTouches,
            activeTouchesMap = me.activeTouchesMap,
            changedTouches = [],
            touchSource, identifier, touch, target, i, ln, x, y;

        for (i = 0, ln = touchSources.length; i < ln; i++) {
            touchSource = touchSources[i];

            if ('identifier' in touchSource) {


                identifier = touchSource.identifier;
            } else if ('pointerId' in touchSource) {

                identifier = touchSource.pointerId;
            } else {


                identifier = 1;
            }

            touch = activeTouchesMap[identifier];

            if (isEnd) {
                delete activeTouchesMap[identifier];
                Ext.Array.remove(activeTouches, touch);
            } else if (!touch) {
                target = Ext.event.Event.resolveTextNode(touchSource.target);
                touch = activeTouchesMap[identifier] = {
                    identifier: identifier,
                    target: target,








                    targets: me.getPropagatingTargets(target)
                };
                activeTouches.push(touch);
            }

            x = touchSource.pageX;
            y = touchSource.pageY;

            touch.pageX = x;
            touch.pageY = y;

            touch.point = new Ext.util.Point(x, y);
            changedTouches.push(touch);
        }




        e.touches = Ext.Array.clone(activeTouches);

        e.changedTouches = changedTouches;
    },

    onDelegatedEvent: function (e) {
        var me = this;



        e = me.callParent([e, false]);






        if (e) {
            if (!e.button || e.button < 1) {



                me.handlers[e.type].call(me, e);
            }




            me.afterEvent(e);
        }
    },

    onTouchStart: function (e) {
        var me = this,
            target = e.target;

        if (e.browserEvent.type === 'touchstart') {






            target.addEventListener('touchmove', me.onTargetTouchMove);
            target.addEventListener('touchend', me.onTargetTouchEnd);
            target.addEventListener('touchcancel', me.onTargetTouchEnd);
        }

        me.updateTouches(e);

        if (!me.isStarted) {


            me.isStarted = true;
            me.invokeRecognizers('onStart', e);




            if (Ext.enableGarbageCollector) {
                Ext.dom.GarbageCollector.pause();
            }
        }
        me.invokeRecognizers('onTouchStart', e);
    },

    onTouchMove: function (e) {
        if (this.isStarted) {
            this.updateTouches(e);
            if (e.changedTouches.length > 0) {
                this.invokeRecognizers('onTouchMove', e);
            }
        }
    },




    onTouchEnd: function (e) {
        var me = this;

        if (!me.isStarted) {
            return;
        }

        me.updateTouches(e, true);

        me.invokeRecognizers(me.isCancelEvent[e.type] ? 'onTouchCancel' : 'onTouchEnd', e);

        if (!me.activeTouches.length) {

            me.isStarted = false;
            me.invokeRecognizers('onEnd', e);



            if (Ext.enableGarbageCollector) {
                Ext.dom.GarbageCollector.resume();
            }
        }
    },

    onTargetTouchMove: function (e) {


        if (!Ext.getBody().contains(e.target)) {
            this.onTouchMove(new Ext.event.Event(e));
        }
    },

    onTargetTouchEnd: function (e) {
        var me = this,
            target = e.target;

        target.removeEventListener('touchmove', me.onTargetTouchMove);
        target.removeEventListener('touchend', me.onTargetTouchEnd);
        target.removeEventListener('touchcancel', me.onTargetTouchEnd);













        if (!Ext.getBody().contains(target)) {
            me.onTouchEnd(new Ext.event.Event(e));
        }
    }

}, function () {
    var handledEvents = [],
        supports = Ext.supports,
        supportsTouchEvents = supports.TouchEvents;

    if (supports.PointerEvents) {
        handledEvents.push('pointerdown', 'pointermove', 'pointerup', 'pointercancel');
    } else if (supports.MSPointerEvents) {

        handledEvents.push('MSPointerDown', 'MSPointerMove', 'MSPointerUp', 'MSPointerCancel');
    } else if (supportsTouchEvents) {
        handledEvents.push('touchstart', 'touchmove', 'touchend', 'touchcancel');
    }

    if (!handledEvents.length || (supportsTouchEvents && Ext.isWebKit && Ext.os.is.Desktop)) {




        handledEvents.push('mousedown', 'mousemove', 'mouseup');
    }

    this.prototype.handledEvents = handledEvents;
});

